package ajaxjs.js;

import java.io.FileNotFoundException;
import java.util.HashMap;
import java.util.Map;

import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptException;

import sun.org.mozilla.javascript.internal.NativeArray;
import sun.org.mozilla.javascript.internal.NativeObject;
import ajaxjs.Fso;

public class SimpleJsEngine implements JsRuntime, Java_Js_Mapper {
	private ScriptEngine js_engine = new javax.script.ScriptEngineManager().getEngineByExtension("js");
	
	/**
	 * 加载 js 文件
	 * @param fullFilePath
	 * @return
	 */
	public String load(String fullFilePath){
		String code = "";
		System.out.println("加载 js 文件：" + fullFilePath);
		
		try {
			code = Fso.readFile(fullFilePath);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			System.err.println("加载文件 " + fullFilePath + "的时候，磁盘找不到该文件！");
		}
		
		try {
			eval(code);
		} catch (ScriptException e) {
			System.err.println("加载文件 " + fullFilePath + "的时候，js 引擎发现语法错误！请修正 js 里的问题！");
		}
		
		return code;
	}
	
	@Override
	public void load(String[] fullFilePaths){
		for(String fullFilePath : fullFilePaths){
			load(fullFilePath);
		}
	}
	
	/**
	 * 运行 js 代码
	 * @param jsSource
	 * @return
	 */
	public Object eval(String jsCode) throws ScriptException{
		Object result = null;
		
		if(jsCode == null || jsCode.equals("")){
			System.err.println("传入 jsCode 为空，请输入代码");
		}else{
			try{
				result = js_engine.eval(jsCode);
			}catch(ScriptException e){
//				System.out.println("脚本执行块：" + jsCode + " 异常！");
				throw e;
			}
		}
			
		return result;
	}
	
	/**
	 * 脚本引擎本身并不负责执行在脚本中定义的方法，而是转换为 javax.script.Invocable 的对象 inv,有inv.invokeFunction() 方法来调用脚本的方法。
	 * invokeFunction() 方法的第一个参数必须是方法名，后面是参数列表。
	 * @param methodName
	 * @param arg Object result1 = jsInvoke.invokeFunction(methodName, new Object[] { 10, 5 });
	 * @return
	 * @throws ScriptException 
	 */
	public Object call(String methodName, Object...arg) throws ScriptException{
		Object result = null;
		
		Invocable inv = (Invocable)js_engine;  
		
		try { 
			// Invocable 接口是 ScriptEngine可选实现的接口。（多态）
		    result = inv.invokeFunction(methodName, arg);  
		}catch(NoSuchMethodException ex) {  
			System.out.println("没有" + methodName + "这个方法");
		}catch(javax.script.ScriptException e) {  
			System.out.println("脚本执行" + methodName + "() 异常！");
			throw e;
		}
	
		return result;
	}
	
	/**
	 * 在 Java 中向脚本引擎 (Script Engine) 传递变量，即脚本语言用 java 的变量。
	 * 当然，使用 eval() 也可以了。注意可以直接赋值 Java 对象。
	 */
	public void put(String varName, Object obj){
		js_engine.put(varName, obj);
	}
	
	public Map<String, Object> NativeObject2Hash(String jsCode){
		Map<String, Object> hash = null;

		if(jsCode == null || jsCode.equals("")){
			System.err.println("NativeObject2Hash :传入 jsCode 为空，请输入代码");
		}else{	
			Object obj = null;
			try{
				eval("var json = " + jsCode);
				obj = eval("json");
			}catch(ScriptException e) {
				System.out.println("JS 代码错误：var json = " + jsCode);
				e.printStackTrace();
				return null;
			}
			
			hash = NativeObject2Hash(obj);
		}
		
		return hash;
	}
	
	/**
	 * JS 对象 转换到 Java Hash 对象
	 * @param obj
	 * @return
	 */
	public Map<String, Object> NativeObject2Hash(NativeObject obj){
	    Map<String, Object> hash = new HashMap<String, Object>();
	    
	    Object[] ids = obj.getAllIds();// 遍历对象
	    String newId;
	    Object value;
	    
	    for (Object id : ids) {
	       newId = (String) id;
	       value = obj.get(newId, obj);
	        
	       if(value instanceof Boolean){
	    	   hash.put(newId, value);
		   }else if(value instanceof String){
	    	   hash.put(newId, value);
	       }else if(value instanceof Double){
	    	   hash.put(newId, ((Double)value).intValue());// js number 转换为 int
	       }else if(value instanceof NativeObject){
	    	   hash.put(newId, NativeObject2Hash(value));
	       }else if(value instanceof NativeArray){
	    	   hash.put(newId, NativeArray2Map(value)); // 这是规则的情况，数组中每个都是对象，而非 string/int/boolean @todo
	       }else{
	    	   if(value != null){
	    		   System.out.println(value.getClass().getName());
	    	   }else{
	    		   hash.put(newId, null); // js 为 null，所以 java hash 也为null
	    	   }
	       }
	    }
	    
	    return hash;
	}
	
	public Map<String, Object> NativeObject2Hash(Object obj){
		return obj == null ? null : NativeObject2Hash((NativeObject)obj);
	}
	
	/**
	 * 输入 js Array 代码（字符串），返回  Java Map List
	 * @param js_code
	 * @return
	 */
	public Map<String, Object>[] NativeArray2Map(String js_code){
		Object arr;
		
		try {
			eval("var arr = " + js_code);
			arr = eval("arr");
		} catch (ScriptException e) {
			return null;
		}
		
		return arr != null ? NativeArray2Map(arr) : null;
	}
	
	public Map<String, Object>[] NativeArray2Map(NativeArray nativeArr){
	    Long len = nativeArr.getLength();
	    
	    @SuppressWarnings("unchecked")
		Map<String, Object>[] arr  = new HashMap[len.intValue()];
	    
	    // JDK7 supprts directly
//		for(Object item : arr){
//			Map<String, Object> hash = NativeObject2Hash((NativeObject)item);
//			list.add(hash);
//		}
	    
	    // JDK6
	    for (Object o : nativeArr.getIds()) {
	        Integer index = (Integer) o;
	        // what about arr. not nativeObj
	        // either obj or arr here
	        if(nativeArr.get(index, nativeArr) instanceof NativeObject){
	        	NativeObject nativeObj = (NativeObject)nativeArr.get(index, nativeArr);
	        	arr[index.intValue()] = NativeObject2Hash(nativeObj);
	        }else if(nativeArr.get(index, nativeArr) instanceof NativeArray){
	        	System.out.println("NativeArray");
	        }
	    }

	    return arr;
	}
	@SuppressWarnings("unchecked")
	public Map<String, Object>[] NativeArray2Map(Object obj){
	    if(obj != null && !obj.equals("")){
	        NativeArray _NativeArray = (NativeArray)obj;
	        return NativeArray2Map(_NativeArray);
	    }else return new HashMap[0];// 写成 new HashMap<String, Object>[0] 报错
	}
	
	/**
	 * 将POJO对象转换成JSON格式的字符串:JAVA-->JS
	 * http://will-turner.iteye.com/blog/1506416
	 * http://www.myexception.cn/javascript/728974.html
	 * 
	public void printErr(Exception error, String customErrMsg)throws IOException{
	    resp.setContentType("application/json");
	    
	    final String _msg = String.format("发生异常。异常信息：%s %s %s", 
	        error == null ? "" : error.getMessage(), 
	        error == null ? "" : error.toString(), 
	        customErrMsg == null ? "" : customErrMsg
	    );

	    respWriter.println(parseObj(new Object() {  
	        public Boolean isOk = false;   
	        public String msg = _msg;
	    }));
	    respWriter.flush();
	}
	 * @param obj
	 * @return
	 */
	public String parseObj(Object obj){
	    if (obj == null) {
	        return "";
	    }
	    Class<?> clazz = obj.getClass();
	    java.lang.reflect.Field[] fields = clazz.getDeclaredFields();
	    
	    StringBuilder buff = new StringBuilder();
	   
	    buff.append("{");

	    for (java.lang.reflect.Field f : fields) {
	        String fieldName; Object value;
	        
	        try{
	            fieldName = f.getName();
	            f.setAccessible(true);
	            if(fieldName.indexOf("this$") != -1)continue;
	            value = f.get(obj);
	            
//	            System.out.println(fieldName + "--------" + value);
	            buff.append("\"");
	            buff.append(fieldName);
	            buff.append("\":");

	            if(value == null){
	                buff.append("\"\",");
	                continue;
	            }
	            if(value instanceof Boolean){
	                buff.append((Boolean)value);
	                buff.append(",");
	            }else if(value instanceof Number){
	                buff.append((Number)value);
	                buff.append(",");
	            }else if(value instanceof java.util.Date){
	                buff.append("\"");
	                buff.append(new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format((java.util.Date)value));
	                buff.append("\",");
	            }else if(value instanceof Object[]){
	                Object[] arr = (Object[])value;
	                String str = "";
	                
	                for(int i = 0; i < arr.length; i++){
	                    if(arr[i] instanceof String){
	                        str += ("\"" + arr[i] + "\"").replace("\\", "\\\\");
	                    }else{
	                        str += parseObj(arr[i]);
	                    }
	                    
	                    if(i != arr.length - 1)str += ",";
	                }

	                buff.append("[");
	                buff.append(str);
	                buff.append("]o");// ??????????????//
	            }else{
	                buff.append("\"");
	                buff.append(value.toString().replace("\\", "\\\\").replace("\"", "\\\""));
	                buff.append("\",");
	            }
	            
	        }catch(IllegalArgumentException e){
	        	e.printStackTrace();
	        }catch (IllegalAccessException e) {
	        	e.printStackTrace();
	        //}catch(java.lang.reflect.InvocationTargetException e) {
	            //e.printStackTrace();
	        }
	    }

	    if (buff.length() > 1) {
	        buff = buff.deleteCharAt(buff.length() - 1);
	    }
	    buff.append("}");

	    return buff.toString();
	}
	public static int jsNumber2int(Object obj) {
		if(obj instanceof Integer)return (Integer) obj;
		Double d_obj = (Double) obj;
		return d_obj.intValue();
	}
	
}
